﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Xml;

namespace EmbeddedSensorCloud.Plugin.Temperature
{
    internal class TemperatureData
    {
        public DateTime Date { get; set; }
        public float Value { get; set; }
    }

    internal class ResponseCreator
    {
        private const int ENTRIES_PER_PAGE = 10;
        private readonly TempDBManager _Database;
        public event EventHandler<Exception> OnTimerException;

        public ResponseCreator(string server, string database, string tableName, string uid, string password)
        {
            this._Database = new TempDBManager(server, database, tableName, uid, password);
        }

        #region GetFunctions

        /// <summary>
        /// Retrieves from the DB the top 10 entries at a given page number.
        /// </summary>
        /// <param name="page">The page number.</param>
        /// <returns>A ready to insert HTML formatted list of values.</returns>
        public string GetDataOfPage(string page)
        {
            StringBuilder foundData = new StringBuilder("<ul>");
            
            int iPage;
            if (int.TryParse(page, out iPage))
            {
                List<TemperatureData> temperatureList = this._Database.GetDataOfPage(iPage, ENTRIES_PER_PAGE);
                foreach (TemperatureData entry in temperatureList)
                {
                    foundData.AppendFormat("<li><b>{0}:</b> {1} °C</li>", entry.Date.ToString("dd.MM.yyyy HH:mm:ss", CultureInfo.InvariantCulture), entry.Value);
                }
            }
            foundData.Append("</ul>");

            return foundData.ToString();
        }

        /// <summary>
        /// Retrievs the number of data entries and calculates the number of pages based on the constant ENTRIES_PER_PAGE.
        /// </summary>
        /// <returns>A ready to insert HTML formatted list of values.</returns>
        public string GetPageNumbers()
        {
            int dataCount = this._Database.GetTempDataCount();
            int filler = (ENTRIES_PER_PAGE - (dataCount % ENTRIES_PER_PAGE));

            int pageCount = (dataCount + filler) / ENTRIES_PER_PAGE;
            StringBuilder pageNumbers = new StringBuilder();

            while (pageCount > 0)
            {
                pageNumbers.Insert(0, string.Format("<a href=\"?page={0}\">{0}</a>", pageCount));
                pageCount--;
            }

            return pageNumbers.ToString();
        }

        /// <summary>
        /// Searches the DB for all temperature data at a given date.
        /// </summary>
        /// <param name="dateToSearch">The date to search for.</param>
        /// <returns>A ready to insert HTML formatted list of values.</returns>
        public string GetDataAtDate(string dateToSearch)
        {
            StringBuilder foundData = new StringBuilder();

            DateTime searchDate;
            if (DateTime.TryParseExact(dateToSearch, "dd.MM.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out searchDate))
            {
                List<TemperatureData> temperatureList = this._Database.GetDataAtDate(searchDate);

                if (temperatureList.Count == 0)
                {
                    foundData.AppendFormat("<h2>No data found for: {0}</h2>", searchDate.ToString("dd.MM.yyyy"));
                }
                else
                {
                    foundData.AppendFormat("<h2>{0}:</h2><ul>", searchDate.ToString("dd.MM.yyyy"));
                    temperatureList.ForEach(data => foundData.AppendFormat("<li>{0} °C</li>", data.Value));
                    foundData.Append("</ul>");
                }
            }
            else
            { foundData.Append("<h2>Invalid date given!</h2>"); }

            return foundData.ToString();
        }

        /// <summary>
        /// Creates a complete valid XML-Document with the temperature data found in it.
        /// </summary>
        /// <param name="requestURL">The URL of the request.</param>
        /// <returns>A complete valid XML-Document.</returns>
        public string CreateRESTDocument(string requestURL)
        {
            XmlDocument restDoc = new XmlDocument();
            XmlDeclaration firstLine = restDoc.CreateXmlDeclaration("1.0", "utf-8", "yes");
            restDoc.AppendChild(firstLine);

            string failMessage = "<fail>Request was invalid.</fail>";

            string[] urlParts = requestURL.Split(new char[] { '/' }, 2, StringSplitOptions.RemoveEmptyEntries);
            if (urlParts.Length < 2) { return (restDoc.OuterXml + failMessage); }

            string[] dateParts = urlParts[1].Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            if (dateParts.Length < 1 || dateParts.Length > 3) { return (restDoc.OuterXml + failMessage); }

            DateTime begin;
            DateTime end;
            string[] dateFormats = { "yyyy", "yyyy/MM", "yyyy/MM/dd" };

            if (!DateTime.TryParseExact(urlParts[1], dateFormats[dateParts.Length - 1], CultureInfo.InvariantCulture, DateTimeStyles.None, out begin) ||
                !DateTime.TryParseExact(urlParts[1], dateFormats[dateParts.Length - 1], CultureInfo.InvariantCulture, DateTimeStyles.None, out end))
            { return (restDoc.OuterXml + failMessage); }


            switch (dateParts.Length)
            {
                case 1:
                    end = new DateTime(end.Year, 12, 31);
                    break;
                case 2:
                    end = new DateTime(end.Year, end.Month, DateTime.DaysInMonth(end.Year, end.Month));
                    break;
                case 3:
                    end = new DateTime(end.Year, end.Month, end.Day, 23, 59, 59, 999);
                    break;
            }

            List<TemperatureData> temperatureList = this._Database.GetDataAtRange(begin, end);

            XmlElement rootElement = restDoc.CreateElement("temperatureRestYear");
            XmlAttribute attValue = restDoc.CreateAttribute("value");
            attValue.Value = begin.Year.ToString();
            rootElement.Attributes.Append(attValue);
            restDoc.AppendChild(rootElement);

            int oldMonth = -1;
            int oldDay = -1;
            XmlElement monthNode = null;
            XmlElement dayNode = null;
            foreach (TemperatureData entry in temperatureList)
            {
                CreateNewNodes(restDoc, ref monthNode, ref dayNode, entry.Date, ref oldMonth, ref oldDay);

                XmlElement valueNode = restDoc.CreateElement("value");
                attValue = restDoc.CreateAttribute("time");
                attValue.Value = entry.Date.TimeOfDay.TotalSeconds.ToString();
                valueNode.Attributes.Append(attValue);
                valueNode.AppendChild(restDoc.CreateTextNode(entry.Value.ToString()));

                dayNode.AppendChild(valueNode);
            }
            return restDoc.OuterXml;
        }

        /// <summary>
        /// Creates new instances for monthNode and dayNode if necessary.
        /// </summary>
        private void CreateNewNodes(XmlDocument doc, ref XmlElement monthNode, ref XmlElement dayNode, DateTime entryDate, ref int oldMonth, ref int oldDay)
        {
            XmlAttribute attValue;
            if (entryDate.Month != oldMonth)
            {
                monthNode = doc.CreateElement("temperatureRestMonth");
                attValue = doc.CreateAttribute("value");
                attValue.Value = entryDate.Month.ToString();
                monthNode.Attributes.Append(attValue);

                doc.ChildNodes[1].AppendChild(monthNode);
                oldMonth = entryDate.Month;

                dayNode = doc.CreateElement("temperatureRestDay");
                attValue = doc.CreateAttribute("value");
                attValue.Value = entryDate.Day.ToString();
                dayNode.Attributes.Append(attValue);

                monthNode.AppendChild(dayNode);
                oldDay = entryDate.Day;
            }
            else if (entryDate.Month == oldMonth && entryDate.Day != oldDay)
            {
                dayNode = doc.CreateElement("temperatureRestDay");
                attValue = doc.CreateAttribute("value");
                attValue.Value = entryDate.Day.ToString();
                dayNode.Attributes.Append(attValue);

                if (monthNode != null)
                {
                    monthNode.AppendChild(dayNode);
                    oldDay = entryDate.Day;
                }
            }
        }

        #endregion

        #region WriteFunction -> Timer callback

        /// <summary>
        /// Inserts a random temperature value to the DB. Note: Should be called continuesly from a timer.
        /// </summary>
        /// <param name="state">The state parameter needed for the callback delegate of a timer.</param>
        public void WriteSensorDataToDB(object state)
        {
            Random sensor = new Random();

            int sensorValue = sensor.Next(40);
            double decimalDigits = Math.Round(sensor.NextDouble(), 2, MidpointRounding.AwayFromZero);
            float sensorData = (float)sensorValue + (float)decimalDigits;

            try
            {
                this._Database.InsertData(sensorData);
            }
            catch (Exception ex)
            {
                if (this.OnTimerException != null)
                { this.OnTimerException(this, ex); }
            }
        }

        #endregion
    }
}
